package com.sweet.cls;

import com.sweet.lang.StringUtil;
import lombok.extern.slf4j.Slf4j;

import java.io.File;
import java.io.FileFilter;
import java.net.JarURLConnection;
import java.net.URL;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;

/**
 * 类加载器
 *
 * @author limi
 * @version 2015-10-30
 */
@Slf4j
public final class ClassUtil {
    private ClassUtil() {
    }

    /**
     * 获取默认类加载器,仿spring源码
     *
     * @return 类加载器
     */
    public static ClassLoader getClassLoader() {
        ClassLoader cl = null;

        try {
            // 优先使用当前线程中的ClassLoader
            cl = Thread.currentThread().getContextClassLoader();
        } catch (Throwable ex) {
            // Cannot access thread context ClassLoader - falling back...
            // 无法访问线程上下文类加载器
        }

        if (cl == null) {
            // No thread context class loader -> use class loader of this class.
            // 没有线程上下文类加载器，则使用当前类的类加载器
            cl = ClassUtil.class.getClassLoader();
            if (cl == null) {
                // returning null indicates the bootstrap ClassLoader
                // 使用系统类加载器
                try {
                    cl = ClassLoader.getSystemClassLoader();
                } catch (Throwable ex) {
                    // Cannot access system ClassLoader - oh well, maybe the
                    // caller can live with null...
                    // 不能访问系统类加载器，只能给个null了
                }
            }
        }
        return cl;
    }

    /**
     * 加载类
     *
     * @param className     类的全称
     * @param isInitialized 是否执行类的静态代码块，不执行则可以提高加载类的性能
     * @param classLoader   类加载器
     * @return 类类型
     * @throws ClassNotFoundException 类找不到异常
     */
    public static Class<?> loadClass(String className, boolean isInitialized, ClassLoader classLoader) throws ClassNotFoundException {
        if (classLoader == null) {
            classLoader = getClassLoader();
        }

        Class<?> cls;
        try {
            cls = Class.forName(className, isInitialized, classLoader);
        } catch (ClassNotFoundException e) {
            log.error("load class failure!", e);
            throw e;
        }

        return cls;
    }

    /**
     * 创建实例
     *
     * @param cls 类类型
     * @return 类实例对象
     */
    public static Object newInstance(Class<?> cls) {
        Object instance;
        try {
            instance = cls.newInstance();
        } catch (Exception e) {
            log.error("Class create new instance failure!", e);
            throw new RuntimeException(e);
        }

        return instance;
    }

    /**
     * 加载包中所有类，支持jar
     * 默认不初始化类，只有使用时初始化
     *
     * @param packageName 包全称
     * @return 包下的所有类类型集合
     * @throws ClassNotFoundException 类找不到异常
     */
    public static Set<Class<?>> getClasses(String packageName) throws ClassNotFoundException {
        // 定义包下所有类类型
        Set<Class<?>> classSet = new HashSet();

        // 获取包下所有类名
        Set<String> classNames = getClassNames(packageName);
        if (classNames != null) {
            ClassLoader classLoader = getClassLoader();
            for (String className : classNames) {
                Class<?> cls = loadClass(className, false, classLoader);

                classSet.add(cls);
            }
        }

        return classSet;
    }

    /**
     * 获取指定包中的所有类全称，支持jar
     *
     * @param packageName 包名
     * @return 包下所有类全称集合
     */
    public static Set<String> getClassNames(String packageName) {
        Set<String> classNames = new HashSet<String>();
        try {
            URL url;
            Enumeration<URL> urls = getClassLoader().getResources(packageName.replace(".", "/"));
            while (urls.hasMoreElements()) {
                url = urls.nextElement();
                if (url != null) {
                    // 获取文件协议，检查当前url属于jar，还是file
                    String protocl = url.getProtocol();

                    log.info("Scan package [{}] from {} : ", packageName, protocl);

                    if (protocl.equals("file")) {
                        String packagePath = url.getPath().replaceAll("%20", "");

                        addClassName(classNames, packagePath, packageName);
                    } else if (protocl.equals("jar")) {
                        JarURLConnection jarURLConnection = (JarURLConnection) url.openConnection();
                        if (jarURLConnection != null) {
                            JarFile jarFile = jarURLConnection.getJarFile();
                            if (jarFile != null) {
                                Enumeration<JarEntry> jarEntries = jarFile.entries();
                                while (jarEntries.hasMoreElements()) {
                                    JarEntry jarEntry = jarEntries.nextElement();
                                    String jarEntryName = jarEntry.getName();

                                    // 只加载.class结尾的类全称
                                    if (jarEntryName.endsWith(".class")) {
                                        classNames.add(jarEntryName.substring(0, jarEntryName.lastIndexOf(".")).replaceAll("/", "."));
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            log.error("get classes from package failure!", e);
            throw new RuntimeException(e);
        }

        return classNames;
    }

    /**
     * 将包中的类加入classes集合中
     *
     * @param classes     集合
     * @param packagePath 包绝对路径
     * @param packageName 包名，用来拼接文件名，组成完整的类名
     */
    private static void addClassName(Set<String> classes, String packagePath, String packageName) {
        // 获取包路径下的所有class文件或目录
        File[] files = new File(packagePath).listFiles(new FileFilter() {
            @Override
            public boolean accept(File file) {
                return (file.isFile() && file.getName().endsWith(".class")) || file.isDirectory();
            }
        });

        // 遍历文件及子目录
        for (File file : files) {
            log.info("Scan package [{}],packagePath [{}],file [{}]", new Object[]{packageName, packagePath, file.getAbsolutePath()});

            String fileName = file.getName();
            if (file.isFile()) {
                // 定义类全称，去掉.class,并与package包名合并
                String className = fileName.substring(0, fileName.lastIndexOf("."));
                if (StringUtil.isNotEmpty(packageName)) {
                    className = packageName + "." + className;
                }

                classes.add(className);
            } else {
                // 定义子包完整路径
                String subPackagePath = file.getName();
                if (StringUtil.isNotEmpty(packagePath)) {
                    subPackagePath = packagePath + "/" + subPackagePath;
                }

                // 定义子包完整包名
                String subPackageName = file.getName();
                if (StringUtil.isNotEmpty(packageName)) {
                    subPackageName = packageName + "." + subPackageName;
                }

                // 递归执行
                addClassName(classes, subPackagePath, subPackageName);
            }
        }
    }
}
